package br.unicamp.ic.mc747;

import interfacesFerrovia.DefsGlobais.TipoAtributo;
import interfacesFerrovia.DefsGlobais.TipoElemento;

import java.util.ArrayList;
import java.util.List;

import org.omg.CORBA.FREE_MEM;

import malhaFerroviaria.Ferrovia;

/**
 * This class manage the events of railway
 * 
 * @authors Guilherme Martinez Sampaio, Marcelo Nagatomo, Rodrigo Souza
 * @version 1.1
 */
public class Monitor {

	// atributos
	Ferrovia ferrovia;
	Chave chaves[];
	String conflitoNaChave[];
	String composicoesConflitantes[][];
	AgendaDeAcoes agenda;
	String composicoes[];
	boolean debug = false;

	// construtor
	public Monitor(Ferrovia ferrovia) {
		this.ferrovia = ferrovia;
		// pega todas as chaves da ferrovia
		getTodasChavesFerrovia();
		conflitoNaChave = new String[chaves.length];
		composicoesConflitantes = new String[chaves.length][];
		composicoes = new String[0];
		for (int i = 0; i < chaves.length; i++) {
			conflitoNaChave[i] = null;
			composicoesConflitantes[i] = new String[0];
		}

		agenda = AgendaDeAcoes.getInstance();
	}

	/**
	 * Armazena todas as chaves da ferrovia, para poder calcular possiveis
	 * conflitos
	 */
	private void getTodasChavesFerrovia() {
		String[] elements = ferrovia.consultaElementos();
		ArrayList<Chave> array = new ArrayList<Chave>();
		for (String e : elements) {
			TipoElemento tipo = (TipoElemento) ferrovia.consultaAtributo(e,
					TipoAtributo.TIPO);
			// Se o elemento for uma chave
			if (tipo.equals(TipoElemento.CHAVE)) {
				Chave chave = new Chave();
				chave.setId(((String) ferrovia.consultaAtributo(e,
						TipoAtributo.ID)));
				chave.setEmMudanca((Boolean) ferrovia.consultaAtributo(e,
						TipoAtributo.EM_MUDANCA));
				chave.setTempoConclusao((Integer) ferrovia.consultaAtributo(e,
						TipoAtributo.TEMPO_CONCLUSAO));
				chave.setTempoDeMudanca((Integer) ferrovia.consultaAtributo(e,
						TipoAtributo.TEMPO_MUDANCA));
				chave.setTrechosA((String[]) ferrovia.consultaAtributo(e,
						TipoAtributo.TRECHOS_A));
				chave.setTrechosB((String[]) ferrovia.consultaAtributo(e,
						TipoAtributo.TRECHOS_B));
				array.add(chave);
			}
		}

		chaves = new Chave[array.size()];
		chaves = array.toArray(chaves);
	}

	/**
	 * Pega a composicao do monitor que tenha o id especificado
	 * 
	 * @param idComposicao
	 * @return retorna a composicao
	 */
	public Composicao getComposicaoPorId(String idComposicao) {
		String[] elements = ferrovia.consultaElementos();

		for (String e : elements) {
			TipoElemento tipo = (TipoElemento) ferrovia.consultaAtributo(e,
					TipoAtributo.TIPO);

			// Se o elemento for uma composicao
			if (tipo.equals(TipoElemento.COMPOSICAO)) {
				if (idComposicao.equals(((String) ferrovia.consultaAtributo(e,
						TipoAtributo.ID)))) {
					Composicao composicao = new Composicao();

					// Consulta todos os atributos da composicao e coloca no
					// objeto
					composicao.setId((String) ferrovia.consultaAtributo(e,
							TipoAtributo.ID));
					composicao.setComprimento((Integer) ferrovia
							.consultaAtributo(e, TipoAtributo.COMPRIMENTO));
					composicao.setVelocidadeMaxima((Float) ferrovia
							.consultaAtributo(e, TipoAtributo.VELOCIDADE_MAX));
					composicao.setAceleracaoMaxima((Float) ferrovia
							.consultaAtributo(e, TipoAtributo.ACELERACAO_MAX));
					composicao.setFrenagemMaxima((Float) ferrovia
							.consultaAtributo(e, TipoAtributo.FRENAGEM_MAX));
					composicao.setItinerario((String[]) ferrovia
							.consultaAtributo(e, TipoAtributo.ITINERARIO));
					composicao.setPartida((Integer) ferrovia.consultaAtributo(
							e, TipoAtributo.PARTIDA));
					composicao.setTrechoAtual((String) ferrovia
							.consultaAtributo(e, TipoAtributo.ID_TR_CH_ATUAL));
					composicao.setPosicaoAtual((Float) ferrovia
							.consultaAtributo(e, TipoAtributo.POSICAO_ATUAL));
					composicao.setVelocidadeAtual((Float) ferrovia
							.consultaAtributo(e, TipoAtributo.VEL_ATUAL));
					composicao.setSentidoAB((Boolean) ferrovia
							.consultaAtributo(e, TipoAtributo.SENTIDO_ATUAL));
					composicao.setAceleracaoAtual((Float) ferrovia
							.consultaAtributo(e, TipoAtributo.ACEL_ATUAL));
					composicao.setFrenagemAtual((Float) ferrovia
							.consultaAtributo(e, TipoAtributo.FREN_ATUAL));
					composicao.setEmMovimento((Boolean) ferrovia
							.consultaAtributo(e, TipoAtributo.EM_MOVIMENTO));

					return composicao;
				}
			}
		}

		//System.err.println("*** WARN: Composicao nao encontrada!! ***");
		return null;
	}

	/**
	 * Pega o trecho do monitor que tenha o id especificado
	 * 
	 * @param idTrecho
	 * @return retorna o trecho
	 */
	public Trecho getTrechoPorId(String idTrecho) {
		String[] elements = ferrovia.consultaElementos();

		for (String e : elements) {
			TipoElemento tipo = (TipoElemento) ferrovia.consultaAtributo(e,
					TipoAtributo.TIPO);

			// Se o elemento for um trecho
			if (tipo.equals(TipoElemento.TRECHO)) {
				if (idTrecho.equals(((String) ferrovia.consultaAtributo(e,
						TipoAtributo.ID)))) {
					Trecho trecho = new Trecho();

					// Consulta todos os atributos do trecho e coloco no objeto
					trecho.setId(idTrecho);
					trecho.setDistancia((Float) ferrovia.consultaAtributo(e,
							TipoAtributo.DISTANCIA));
					trecho.setExtremidadeAId((String) ferrovia
							.consultaAtributo(e, TipoAtributo.ID_TR_CH_A));
					trecho.setExtremidadeBId((String) ferrovia
							.consultaAtributo(e, TipoAtributo.ID_TR_CH_B));
					trecho.setNumeroComposicoes((Integer) ferrovia
							.consultaAtributo(e, TipoAtributo.N_COMPOSICOES));

					// retorna trecho
					return trecho;
				}
			}
		}

		// System.err.println("*** WARN: Trecho nao encontrado!! ***");
		return null;
	}

	/**
	 * Pega a chave do monitor que tenha o id especificado
	 * 
	 * @param idChave
	 * @return retorna a chave
	 */
	public Chave getChavePorId(String idChave) {
		if (idChave != null) {
			String[] elements = ferrovia.consultaElementos();

			for (String e : elements) {
				TipoElemento tipo = (TipoElemento) ferrovia.consultaAtributo(e,
						TipoAtributo.TIPO);

				// Se o elemento for uma chave
				if (tipo.equals(TipoElemento.CHAVE)) {
					if (idChave.equals(((String) ferrovia.consultaAtributo(e,
							TipoAtributo.ID)))) {
						Chave chave = new Chave();

						// Consulta todos os atributos da chave e coloco no
						// objeto
						chave.setId(idChave);
						chave.setEmMudanca((Boolean) ferrovia.consultaAtributo(
								e, TipoAtributo.EM_MUDANCA));
						chave.setTempoConclusao((Integer) ferrovia
								.consultaAtributo(e,
										TipoAtributo.TEMPO_CONCLUSAO));
						chave
								.setTempoDeMudanca((Integer) ferrovia
										.consultaAtributo(e,
												TipoAtributo.TEMPO_MUDANCA));
						chave.setTrechosA((String[]) ferrovia.consultaAtributo(
								e, TipoAtributo.TRECHOS_A));
						chave.setTrechosB((String[]) ferrovia.consultaAtributo(
								e, TipoAtributo.TRECHOS_B));

						// retorna chave
						return chave;
					}
				}
			}
		}

		// System.err.println("*** WARN: Trecho não encontrado!! ");
		return null;
	}

	/*
	 * Retorna as composicoes que estao num determinado trecho
	 * 
	 * @param idTrecho
	 * 
	 * @return uma lista de composicoes se existir ou null
	 */
	public List<Composicao> getComposicoesPorTrecho(String idTrecho) {
		List<Composicao> composicoes = new ArrayList<Composicao>();
		String[] elements = ferrovia.consultaElementos();

		for (String e : elements) {
			if ((String) ferrovia.consultaAtributo(e,
					TipoAtributo.ID_TR_CH_ATUAL) == idTrecho) {
				Composicao c = getComposicaoPorId(e);
				composicoes.add(c);
			}
		}

		return composicoes != null && composicoes.size() > 0 ? composicoes
				: null;
	}
	
	/** 
	 * Metodo que retorna todas as composicoes da ferrovia
	 * @return
	 */
	public List<Composicao> getComposicoesAEstacionar() {
		List<Composicao> composicoes = new ArrayList<Composicao>();
		String[] elements = ferrovia.consultaElementos();
		
		for (String e : elements) {
			TipoElemento tipo = (TipoElemento) ferrovia.consultaAtributo(e, TipoAtributo.TIPO);
			Composicao c = null;
			if (tipo == TipoElemento.COMPOSICAO) {
				c = getComposicaoPorId(e);
				String trechoFinal = c.getItinerario()[c.getItinerario().length-1];
				if (c.getTrechoAtual().equals(trechoFinal)) {
					composicoes.add(getComposicaoPorId(e));
				}
			}
		}
		return composicoes;
	}

	public String[] composicaoDisputaItinerario(String idComposicao) {
		// atualiza lista de composicoes na ferrovia
		ArrayList<String> composicoesNaFerrovia = new ArrayList<String>();
		for (int i = 0; i < composicoes.length; i++) {
			composicoesNaFerrovia.add(composicoes[i]);
		}
		composicoesNaFerrovia.add(idComposicao);
		composicoes = composicoesNaFerrovia.toArray(composicoes);

		// verifica se itinerario de idComposicao conflita com
		// algum outro itinerario
		for (int i = 0; i < composicoes.length; i++) {

			// se nao houve conflito com nenhuma outra composicao
			if (composicoes[i].equals(idComposicao)) {
				return null;

			} else {
				Composicao c1 = getComposicaoPorId(composicoes[i]);
				Composicao c2 = getComposicaoPorId(idComposicao);
				if (!c1.getSentidoAB().equals(c2.getSentidoAB())) {
					String itEmAndamento[] = c1.getItinerario();
					String it[] = c2.getItinerario();

					// // apenas conflitos futuros
					int cont1 = 0, cont2 = 0;
					// String trechoAtualAndamento =
					// getComposicaoPorId(composicoes[i]).getTrechoAtual();
					// for (int j=0; j<itEmAndamento.length; j++){
					// if(trechoAtualAndamento.equals(itEmAndamento[j]))
					// break;
					// cont1++;
					// }
					//				
					// String trechoAtualIt =
					// getComposicaoPorId(idComposicao).getTrechoAtual();
					// for (int j=0; j<it.length; j++){
					// if(trechoAtualIt.equals(it[j]))
					// break;
					// cont2++;
					// }

					// para cada trecho da composicao em andamento
					for (int j = cont1; j < itEmAndamento.length; j++) {
						// para cada trecho da composicao que partiu
						for (int k = cont2; k < it.length; k++) {
							// se existe trecho conflitante
							if (itEmAndamento[j].equals(it[k])) {

								String values[] = { composicoes[i], it[k] };
								return values;
							}
						}
					}
				}
			}
		}

		return null;
	}

	/**
	 * Metodo que verifica a existencia de colisao frontal freiando todas a
	 * composicoes que podem bater
	 * 
	 * @param idComposicao
	 */
	public void verificaColisaoFrontalComposicao(String idComposicao) {
		Composicao c = getComposicaoPorId(idComposicao);
		Trecho t = getTrechoPorId(c.getTrechoAtual());
		List<Composicao> composicoes = getComposicoesPorTrecho(t.getId());

		if (composicoes != null) {
			boolean sentido = composicoes.get(0).getSentidoAB();
			boolean existeColisaoFrontal = false;
			for (Composicao comp : composicoes) {
				if (comp.getSentidoAB() != sentido) {
					existeColisaoFrontal = true;
				}
			}

			// Se existe composicoes no mesmo trecho com sentido inverso temos
			// que freiar todas
			if (existeColisaoFrontal) {
				for (Composicao comp : composicoes) {
					if (comp.getVelocidadeAtual() > 0) {
						ferrovia.frearComposicao(comp.getId(), 0, true);
					}
				}
			}
		}
	}

	/**
	 * Verifica se a composicao se encontra na parte final do ultimo trecho do
	 * seu itinerario.
	 * 
	 * @param idComposicao
	 * @param instanteAtual
	 * @return true se a composicao deve comecar a freiar, false se nao
	 */
	public boolean deveDesacelerarParaEstacionar(String idComposicao,
			int instanteAtual) {
		// instancias necessarias para o metodo
		Composicao c = getComposicaoPorId(idComposicao);
		Trecho t = getTrechoPorId(c.getTrechoAtual());
		
		//calculo das distancias e final do percurso
		float distanciaRestante = t.getDistancia() - getComposicaoPorId(idComposicao).getPosicaoAtual(); //ferrovia.consultaPosicao(idComposicao, instanteAtual);
		float distanciaMinima = ferrovia.consultaDistMinParada(idComposicao);
		String trechoFinal = c.getItinerario()[c.getItinerario().length - 1];

		// Verifica se falta menos de 20% do trecho para terminar.
		
		//verifica se ainda existem sensores a frente no trecho
		boolean ultimoSensor;
		if (c.getSentidoAB()) {
			double porcentagem = (distanciaRestante - distanciaMinima)
					/ t.getDistancia();
			// ultimoSensor = (distanciaRestante - distanciaMinima <= 1500);
			ultimoSensor = porcentagem <= 0.2;
		} else {
			//1000 < total - posicao
			double porcentagem = /*ferrovia.consultaPosicao(idComposicao, instanteAtual)*/ getComposicaoPorId(idComposicao).getPosicaoAtual()/t.getDistancia();
//			ultimoSensor = (ferrovia.consultaPosicao(idComposicao, instanteAtual) <= 1500);
			ultimoSensor = porcentagem <=0.2;
		}

		// verifica se a composicao esta no trecho final
		boolean trechoDestino = trechoFinal.equals(c.getTrechoAtual());

		// se eh o �ltimo sensor do trecho e se � o trecho de destino
		return ultimoSensor && trechoDestino;
	}

	/**
	 * Retorna distancia ateh o final do trecho onde a composicao estah.
	 */
	private float getDistanciaAteFinalTrecho(Composicao c, Trecho t,
			int instanteAtual) {
		float distanciaTotal;
		if(c.getSentidoAB()){
			distanciaTotal = t.getDistancia() - c.getPosicaoAtual(); //ferrovia.consultaPosicao(c.getId(), instanteAtual);
		} else {
			distanciaTotal = c.getPosicaoAtual(); //ferrovia.consultaPosicao(c.getId(), instanteAtual);
		}
		return distanciaTotal;
	}

	/**
	 * Retorna o valor de desaceleracao ideal para que a composicao atinja
	 * velocidade 0 no final do trecho. (torricelli)
	 * 
	 * @param idComposicao
	 * @param instanteAtual
	 * @return valor em modulo da desaceleracao ideal para frenagem.
	 */
	public float desaceleracaoIdeal(String idComposicao, int instanteAtual) {
		// instancias necessarias para o m�todo
		Composicao c = getComposicaoPorId(idComposicao);
		Trecho t = getTrechoPorId(c.getTrechoAtual());

		// calcula distancia para final
		float distanciaTotal = getDistanciaAteFinalTrecho(c, t, instanteAtual);

		// Torricelli: a = (V^2 - Vo^2) / 2D
		return (float) (c.getVelocidadeAtual() * c.getVelocidadeAtual())
				/ (float) (2 * distanciaTotal);
	}

	/**
	 * Verifica se eh necessario alterar a posicao da chave imediatamente
	 * posterior ao trecho da composicao idComposicao - e a altera se for
	 * preciso.
	 * 
	 * @param idComposicao
	 * @param instanteAtual
	 */
	public void manobraChave(Composicao c, Chave ch) {
		if (debug)
			System.out.println("[DEBUG] manobraChave(" + c.getId() + ", "
					+ ch.getId() + ");");

		Trecho tr = getProximoTrechoSeComposicaoEstaNaChave(c, ch);
		String proximoTrechoId = tr.getId();

		// composicao no trecho final do itinerario
		if (proximoTrechoId.equals("")) {
			return;
		}

		// chave em mudanca
		if (ch.getEmMudanca()) {
			agendaTrocaDeEstadoDaProximaChave(ch.getId(), c.getTrechoAtual(),
					proximoTrechoId, c.getId());
			return;
		}

		if (c.getSentidoAB()) {
			ferrovia
					.mudarChave(ch.getId(), c.getTrechoAtual(), proximoTrechoId);
		} else {
			ferrovia
					.mudarChave(ch.getId(), proximoTrechoId, c.getTrechoAtual());
		}

		marcaChaveComoSua(c.getId(), ch.getId());

	}

	private void marcaChaveComoSua(String idComposicao, String idChave) {
		for (int i = 0; i < chaves.length; i++) {
			if (idChave.equals(chaves[i].getId())) {
				conflitoNaChave[i] = idComposicao;
			}
		}
	}

	private void agendaTrocaDeEstadoDaProximaChave(String idChave,
			String idTrechoA, String idTrechoB, String idComposicao) {
		if (debug)
			System.out.println("[DEBUG] Troca de Chave Agendada: [" + idChave
					+ "," + idTrechoA + ", " + idTrechoB + "]" + " para "
					+ idComposicao);

		Composicao c = getComposicaoPorId(idComposicao);
		if (c.getSentidoAB())
			agenda
					.addTrocaDeChaves(idChave, idTrechoA, idTrechoB,
							idComposicao);
		else
			agenda
					.addTrocaDeChaves(idChave, idTrechoB, idTrechoA,
							idComposicao);
	}

	public void agendaTrocaDeEstadoDaProximaChave(String idComposicao) {
		// instancias necessarias para o metodo
		Composicao c = getComposicaoPorId(idComposicao);
		Trecho t = getTrechoPorId(c.getTrechoAtual());
		if (t == null) {
			t = getProximoTrechoSeComposicaoEstaNaChave(c);
			c.setTrechoAtual(t.getId());
		}

		String proximoTrechoId = calculaProximoTrecho(c);
		Chave ch = calculaProximaChave(c, t);

		agendaTrocaDeEstadoDaProximaChave(ch.getId(), t.getId(),
				proximoTrechoId, idComposicao);
	}

	public void garanteEstadoDaProximaChave(String idComposicao) {
		// if(debug)
		// System.out.println("[DEBUG] Comp["+idComposicao+", "+instanteAtual+"].manobraChave()");

		// instancias necessarias para o m�todo
		Composicao c = getComposicaoPorId(idComposicao);
		Trecho t = getTrechoPorId(c.getTrechoAtual());
		if (t == null) {
			t = getProximoTrechoSeComposicaoEstaNaChave(c);
			c.setTrechoAtual(t.getId());
		}
		Chave ch = calculaProximaChave(c, t);
		if (ch != null)
			manobraChave(c, ch);
	}

	public void atualizaChave(String idChave) {
		String[] proximoEstado = agenda.getProximaTrocaDeChaves(idChave);

		if (proximoEstado != null) {
			if (debug)
				System.out.println("[DEBUG] Atualiza Chave: ["
						+ proximoEstado[0] + ", " + proximoEstado[1] + ", "
						+ proximoEstado[2] + "]");
			ferrovia.mudarChave(proximoEstado[0], proximoEstado[1],
					proximoEstado[2]);
			marcaChaveComoSua(proximoEstado[3], proximoEstado[0]);
			if (getComposicaoPorId(proximoEstado[3]).getVelocidadeAtual()<getComposicaoPorId(proximoEstado[3]).getVelocidadeMaxima()) {
				ferrovia.acelerarComposicao(proximoEstado[3], getComposicaoPorId(proximoEstado[3]).getVelocidadeMaxima(), 0.2f);
			}
		}
	}

	public void defineItinerarioPrioritario(String idComp1, String idComp2,
			String idTrecho, int instanteAtual) {
		//System.err.println("## comp1 [" + idComp1 + "] disputa trecho <" + idTrecho + "> com [" + idComp2 + "].");

		// instancias necessarias para o metodo
		Trecho tr = getTrechoPorId(idTrecho);
		Composicao c1 = getComposicaoPorId(idComp1);
		Composicao c2 = getComposicaoPorId(idComp2);

		// calcula a ultima chave do conflito
		Chave ch1 = calculaProximaChave(c1, tr);
		Chave ch2 = calculaProximaChave(c2, tr);

		if (ch1 == null || ch2 == null)
			return;

		// calcula as distancias maximas
		float dist1 = distanciaComposicaoAChave(idComp1, ch1.getId(),
				instanteAtual);
		float dist2 = distanciaComposicaoAChave(idComp2, ch2.getId(),
				instanteAtual);

		// System.err.println("## comp1 ["+idComp1+": "+dist1+"].");
		// System.err.println("## comp1 ["+idComp2+": "+dist2+"].");
		if (dist1 < dist2) {
			garanteChavesDeConflito(c1, ch1);
			if (c2.getVelocidadeAtual() > 5) { 
				ferrovia.frearComposicao(c2.getId(), 5, false);
			}
			
		} else {
			garanteChavesDeConflito(c2, ch2);
			if (c1.getVelocidadeAtual() > 5) {
				ferrovia.frearComposicao(c1.getId(), 5, false);
			}
		}

	}

	private void garanteChavesDeConflito(Composicao comp, Chave ch) {
		garanteEstadoDaProximaChave(comp.getId());
		manobraChave(comp, ch);
	}

	// public void liberaChave(String idComp, String idChave){
	// for(int i=0; i<chaves.length; i++){
	// if(idChave.equals(chaves[i])){
	//				
	// }
	// }
	// }

	/**
	 * Calcula a proxima chave da composicao
	 * 
	 * @param c
	 *            composicao
	 * @param t
	 *            trecho
	 * @return proxima chave
	 */
	private Chave calculaProximaChave(Composicao c, Trecho t) {
		// encontra chave que possui conexao com trechoAtual e proximoTrecho
		for (int i = 0; i < chaves.length; i++) {
			if (c.getSentidoAB()) {
				for (int a = 0; a < chaves[i].getTrechosA().length; a++) {
					if (chaves[i].getTrechosA()[a].equals(t.getId())) {
						return chaves[i];

					}
				}
			} else {
				for (int a = 0; a < chaves[i].getTrechosB().length; a++) {
					if (chaves[i].getTrechosB()[a].equals(t.getId())) {
						return chaves[i];
					}
				}
			}
		}
		return null;
	}

	private String calculaProximoTrecho(Composicao c) {
		// pega proximo trecho do itinerario da composicao
		for (int i = 0; i < c.getItinerario().length; i++) {
			if (c.getItinerario()[i].equals(c.getTrechoAtual())) {
				if (i + 1 == c.getItinerario().length) {
					// ultimo trecho do itinerario
					return "";
				} else {
					// existe pelo menos um trecho a ser percorrido
					return c.getItinerario()[i + 1];
				}
			}
		}

		return "";
	}

	/**
	 * Acelera uma composicao e redefine matriz de conflitos entre composicoes
	 * por chaves.
	 * 
	 * @param idChave
	 */
	public void aceleraComposicaoConflitante(String idChave) {
		// encontra chave
		int chaveIndex = -1;
		for (int i = 0; i < chaves.length; i++) {
			if (chaves[i].getId().equals(idChave)) {
				chaveIndex = i;
				break;
			}
		}

		// se existem composicoes esperando para acelerar
		String idsConflitantes[] = composicoesConflitantes[chaveIndex];
		if (idsConflitantes != null && idsConflitantes.length > 0) {
			// pega composicao a ser acelerada e acelera-a
			String idComposicaoAceleravel = idsConflitantes[0];
			if(idComposicaoAceleravel != null){
				if (getComposicaoPorId(idComposicaoAceleravel).getVelocidadeAtual()<getComposicaoPorId(idComposicaoAceleravel).getVelocidadeMaxima()) {
					ferrovia.acelerarComposicao(idComposicaoAceleravel, getComposicaoPorId(idComposicaoAceleravel).getVelocidadeMaxima(), 0.2f);
				}
			}

			// cria nova lista de composicoes desaceleradas esperando liberacao
			// de chave
			ArrayList<String> novosConflitos = new ArrayList<String>();
			for (int i = 1; i < idsConflitantes.length; i++) {
				novosConflitos.add(idsConflitantes[i]);
			}
			composicoesConflitantes[chaveIndex] = novosConflitos
					.toArray(composicoesConflitantes[chaveIndex]);
			if (composicoesConflitantes[chaveIndex].length < 1) {
				conflitoNaChave[chaveIndex] = null;
			}
		}
	}

	/**
	 * Verifica se composicoes disputam a chave idChave
	 * 
	 * @param idChave
	 * @return true se existem composicoes esperando pela chave, false se nao
	 */
	public boolean existeDisputaPorChave(String idChave, String idComposicao) {
		for (int i = 0; i < chaves.length; i++) {
			if (chaves[i].getId().equals(idChave)) {
				if (conflitoNaChave[i] != null
						|| conflitoNaChave[i].equals(idComposicao))
					return true;
				else
					return false;
			}
		}

		// soh chega aqui se a chave nao existir
		return false;
	}

	/**
	 * Retorna distancia da composicao idComposicao ateh a chave idChave
	 * 
	 * @param idComposicao
	 * @param idChave
	 * @param instanteAtual
	 * @return float - distancia da composicao
	 */
	public float distanciaComposicaoAChave(String idComposicao, String idChave,
			int instanteAtual) {
		Chave c = getChavePorId(idChave);
		Composicao c1 = getComposicaoPorId(idComposicao);

		// encontra trecho onde idComposicao encontra a chave
		String trechoComp1PreChave = "";
		for (int i = 0; i < c1.getItinerario().length; i++) {
			if (c1.getSentidoAB()) {
				for (int j = 0; j < c.getTrechosA().length; j++) {
					if (c1.getItinerario()[i].equals(c.getTrechosA()[j])) {
						trechoComp1PreChave = c1.getItinerario()[i];
					}
				}
			} else {
				for (int j = 0; j < c.getTrechosB().length; j++) {
					if (c1.getItinerario()[i].equals(c.getTrechosB()[j])) {
						trechoComp1PreChave = c1.getItinerario()[i];
					}
				}
			}
		}

		float distancia = 0;
		// calcula distancia
		// se jah estah no trecho onde encontra a chave
		ArrayList<String> trechosAteChave = new ArrayList<String>();
		if (trechoComp1PreChave.equals(c1.getTrechoAtual())) {
			distancia = getDistanciaAteFinalTrecho(c1, getTrechoPorId(c1
					.getTrechoAtual()), instanteAtual);
			// se nao
		} else {
			// inclui trechos: do trecho onde encontra a chave ateh o trecho
			// atual
			boolean incluiTrechos = true;
			for (int j = c1.getItinerario().length - 1; j > 0; j--) {
				// trechos intermediarios a serem inclusos
				if (incluiTrechos
						&& !c1.getTrechoAtual().equals(c1.getItinerario()[j])
						&& !c1.getItinerario()[j].equals(trechoComp1PreChave)) {
					trechosAteChave.add(c1.getItinerario()[j]);
				}

				// ultimo trecho a ser incluso (trecho atual)
				if (c1.getTrechoAtual().equals(c1.getItinerario()[j])) {
					incluiTrechos = false;
					trechosAteChave.add(c1.getItinerario()[j]);
				}

				// primeiro trecho a ser incluso (trecho que acaba na chave)
				if (c1.getItinerario()[j].equals(trechoComp1PreChave)) {
					incluiTrechos = true;
					trechosAteChave.add(c1.getItinerario()[j]);
				}
			}
		}

		// inclui na distancia total cada trecho ateh a chave
		for (int i = 0; i < trechosAteChave.size(); i++) {
			// inclui distancia ateh o fim do trecho
			if (trechosAteChave.get(i).equals(c1.getTrechoAtual())) {
				distancia += getDistanciaAteFinalTrecho(c1, getTrechoPorId(c1
						.getTrechoAtual()), instanteAtual);
				// inclui distancia total do trecho
			} else {
				distancia += getDistanciaAteFinalTrecho(c1,
						getTrechoPorId(trechosAteChave.get(i)), instanteAtual);
			}
		}

		if (debug)
			System.out.println("distancia total da composicao " + idComposicao
					+ " ateh a chave " + idChave + " eh: " + distancia);

		return distancia;
	}

	/**
	 * Verifica se a composicao idComposicao disputa com alguma(s) outra(s)
	 * composicoes uma chave. Se a composicao eh a primeira a notar que haverah
	 * uma disputa, ela tem a preferencia. Se nao eh a primeira a notar, se
	 * inclui na lista de composicoes disputando a chave.
	 * 
	 * @param idComposicao
	 * @return true se qualquer outra composicao tem preferencia pela chave,
	 *         false se nao existe disputa pela chave OU se a composicao eh a
	 *         primeira a prever disputa.
	 */
	public boolean composicaoDisputaChave(String idComposicao) {
		// instancias necessarias para o m�todo
		Composicao c = getComposicaoPorId(idComposicao);
		Trecho t = getTrechoPorId(c.getTrechoAtual());
		if (t == null) {
			t = getProximoTrechoSeComposicaoEstaNaChave(c);
			c.setTrechoAtual(t.getId());
		}

		Chave ch;
		if (c.getSentidoAB()) {
			ch = getChavePorId(t.getExtremidadeBId());
		} else {
			ch = getChavePorId(t.getExtremidadeAId());
		}

		if (ch == null)
			return false;

		// int totalComposicoesLadoA = 0;
		//		
		// //para cada trecho ligado ao lado A da chave
		// for(int i = 0; i < ch.getTrechosA().length; i++){
		// //verifica se existem trens no trecho
		// Trecho tr = getTrechoPorId(ch.getTrechosA()[i]);
		// if(tr.getNumeroComposicoes() > 0) {
		// totalComposicoesLadoA++;
		// }
		// }
		//		
		// //composicao idComposicao
		// totalComposicoesLadoA--;

		// se existe disputa por chave
		// if (totalComposicoesLadoA > 0);{
		// verifica se eh a primeira composicao a perceber a disputa
		for (int i = 0; i < chaves.length; i++) {
			if (chaves[i].getId().equals(ch.getId())) {
				// se eh a primeira composicao a notar a disputa
				if (conflitoNaChave[i] == null
						|| conflitoNaChave[i].equals(idComposicao)) {
					conflitoNaChave[i] = idComposicao;
					return false;
					// se a disputa jah era conhecida pelo monitor
				} else {
					// adiciona a composicao a lista de conflitos da chave
					String antigosConflitos[] = composicoesConflitantes[i];
					ArrayList<String> novosConflitos = new ArrayList<String>();
					// copia conflitos antigos
					if (antigosConflitos != null) {
						for (int j = 0; j < antigosConflitos.length; j++) {
							novosConflitos.add(antigosConflitos[j]);
						}
					}
					// redefine lista de conflitos
					novosConflitos.add(idComposicao);
					composicoesConflitantes[i] = novosConflitos
							.toArray(composicoesConflitantes[i]);
					return true;
				}
			}
		}
		// }

		// nao deve alcancar aqui
		return false;
	}

	public void liberaChave(String idComposicao, String idChave) {
		Composicao c = getComposicaoPorId(idComposicao);
		Chave ch = getChavePorId(idChave);

		for (int i = 0; i < chaves.length; i++) {
			if (ch.getId().equals(chaves[i].getId()))
				if (conflitoNaChave[i] != null
						&& conflitoNaChave[i].equals(idComposicao))
					conflitoNaChave[i] = null;
		}
	}

	private Trecho getProximoTrechoSeComposicaoEstaNaChave(Composicao c) {
		Chave ch = getChavePorId(c.getTrechoAtual());
		return getProximoTrechoSeComposicaoEstaNaChave(c, ch);
	}

	private Trecho getProximoTrechoSeComposicaoEstaNaChave(Composicao c,
			Chave ch) {
		String[] trechosAnterior;
		if (c.getSentidoAB()) {
			trechosAnterior = ch.getTrechosA();
		} else {
			trechosAnterior = ch.getTrechosB();
		}

		for (int i = 0; i < c.getItinerario().length; i++) {
			for (int j = 0; j < trechosAnterior.length; j++) {
				if (c.getItinerario()[i].equals(trechosAnterior[j]))
					if (i + 1 == c.getItinerario().length) {
						// ultimo trecho do itinerario
						return null;
					} else {
						// existe pelo menos um trecho a ser percorrido
						return getTrechoPorId(c.getItinerario()[i + 1]);
					}
			}
		}

		// System.err.println("*** WARN: Trecho nao encontrado e composicao nao esta em uma chave!! ***");
		return null;
	}
}
